home *** CD-ROM | disk | FTP | other *** search
/ NeXT Education Software Sampler 1992 Fall / NeXT Education Software Sampler 1992 Fall.iso / Programming / Source / HippoDraw / HippoDrawSrc1.1 / Hippo.subproj / TextGraphic.m < prev    next >
Encoding:
Text File  |  1992-04-25  |  13.5 KB  |  505 lines

  1. #import "TextGraphic.h"
  2. #import "GraphicView.h"
  3. #import <appkit/Application.h>
  4. #import <appkit/Cell.h>
  5. #import <appkit/Cursor.h>
  6. #import <appkit/Font.h>
  7. #import <appkit/Text.h>
  8. #import <appkit/nextstd.h>
  9. #import <dpsclient/wraps.h>
  10.  
  11. @implementation TextGraphic
  12. /*
  13.  * This uses a text object to draw and edit text.
  14.  *
  15.  * The one quirky thing to understand here is that growable Text objects
  16.  * in 1.0 must be subviews of flipped view.  Since a GraphicView is not
  17.  * flipped, we must have a flipped view into the view heirarchy when we
  18.  * edit (this editing view is permanently installed as a subview of the
  19.  * GraphicView--see GraphicView's newFrame: method).
  20.  */
  21.  
  22. + initialize
  23. {
  24.     [self setVersion:1];    /* class version, see read: */
  25.     return self;
  26. }
  27.  
  28. static id drawText = nil;    /* shared Text object used for drawing */
  29. static id drawWindow = nil;
  30.  
  31. static void initClassVars()
  32. /*
  33.  * Create the class variable drawText here.
  34.  */
  35. {
  36.     if (!drawText) {
  37.     drawText = [Text new];
  38.     [drawText setMonoFont:NO];
  39.     [drawText setEditable:NO];
  40.     [drawText setSelectable:NO];
  41.     [drawText setFlipped:YES];
  42.     }
  43. }
  44.  
  45. - init
  46. {
  47.     initClassVars();
  48.     [super init];
  49.     return self;
  50. }
  51.  
  52. - free
  53. {
  54.     free(data);
  55.     return [super free];
  56. }
  57.  
  58. /* Factory methods overridden from superclass */
  59.  
  60. + (BOOL)isEditable
  61. {
  62.     return YES;
  63. }
  64.  
  65. + cursor
  66. {
  67.     return NXIBeam;
  68. }
  69.  
  70. /* Factory method used to show/hide the ruler */
  71.  
  72. + hideRuler:view
  73. /*
  74.  * Tries to hide any rulers that are lying around.
  75.  * If view is nil, obviously it won't hide the ruler
  76.  * (we use this fact to cancel a previous request that
  77.  * we might have made to hide the ruler).
  78.  */
  79. {
  80.     [view tryToPerform:@selector(hideRuler:) with:nil];
  81.     return self;
  82. }
  83.  
  84. /* Instance methods overridden from superclass */
  85.  
  86. - (BOOL)create:(NXEvent *)event in:view
  87.  /*
  88.   * We are only interested in where the mouse goes up, that's
  89.   * where we'll start editing.
  90.   */
  91. {
  92.     NXRect viewBounds;
  93.  
  94.     event = [NXApp getNextEvent:NX_MOUSEUPMASK];
  95.     bounds.size.width = bounds.size.height = 0.0;
  96.     bounds.origin = event->location;
  97.     [view convertPoint:&bounds.origin fromView:nil];
  98.     [view getBounds:&viewBounds];
  99.     gFlags.selected = NO;
  100.  
  101.     return NXMouseInRect(&bounds.origin, &viewBounds, NO);
  102. }
  103.  
  104. - (BOOL)edit:(NXEvent *)event in:view
  105. /*
  106.  * Here we are going to use the shared field editor for the window to
  107.  * edit the text in the TextGraphic.  First, we must end any other editing
  108.  * that is going on with the field editor in this window using endEditingFor:.
  109.  * Next, we get the field editor from the window.  Normally, the field
  110.  * editor ends editing when carriage return is pressed.  This is due to
  111.  * the fact that its character filter is NXFieldFilter.  Since we want our
  112.  * editing to be more like an editor (and less like a Form or TextField),
  113.  * we set the character filter to be NXEditorFilter.  What is more, normally,
  114.  * you can't change the font of a TextField or Form with the FontPanel
  115.  * (since that might interfere with any real editable Text objects), but
  116.  * in our case, we do want to be able to do that.  We also want to be
  117.  * able to edit rich text, so we issue a setMonoFont:NO.  Editing is a bit
  118.  * more efficient if we set the Text object to be opaque.  Note that
  119.  * in textDidEnd:endChar: we will have to set the character filter,
  120.  * FontPanelEnabled and mono-font back so that if there were any forms
  121.  * or TextFields in the window, they would have a correctly configured
  122.  * field editor.
  123.  *
  124.  * To let the field editor know exactly where editing is occurring and how
  125.  * large the editable area may grow to, we must calculate and set the frame
  126.  * of the field editor as well as its minimum and maximum size.
  127.  *
  128.  * We load up the field editor with our rich text (if any).
  129.  *
  130.  * Finally, we set self as the delegate (so that it will receive the
  131.  * textDidEnd:endChar: message when editing is completed) and either
  132.  * pass the mouse-down event onto the Text object, or, if a mouse-down
  133.  * didn't cause editing to occur (i.e. we just created it), then we
  134.  * simply put the blinking caret at the beginning of the editable area.
  135.  *
  136.  * The line marked with the "ack!" is kind of strange, but is necessary
  137.  * since growable Text objects only work when they are subviews of a flipped
  138.  * view.
  139.  *
  140.  * This is why GraphicView has an "editView" which is a flipped view that it
  141.  * inserts as a subview of itself for the purposes of providing a superview
  142.  * for the Text object.  The "ack!" line converts the bounds of the TextGraphic
  143.  * (which are in GraphicView coordinates) to the coordinates of the Text
  144.  * object's superview (the editView).  This limitation of the Text object
  145.  * will be fixed post-1.0.  Note that the "ack!" line is the only one
  146.  * concession we need to make to this limitation in this method (there are
  147.  * two more such lines in textDidEnd:endChar:).
  148.  */
  149. {
  150.     id fe;
  151.     NXSize maxSize;
  152.     NXStream *stream;
  153.     NXRect viewBounds, frame;
  154.  
  155.     /* Get the field editor in this window. */
  156.  
  157.     [[view window] endEditingFor:self];
  158.     fe = [[view window] getFieldEditor:YES for:self];
  159.     if (!fe) return NO;
  160.     [fe setFont:[[FontManager new] selFont]];
  161.  
  162.     /* Show text ruler and abort hiding the ruler if we have recently ended editing. */
  163.  
  164.     [[self class] perform:@selector(hideRuler:) with:nil afterDelay:0 cancelPrevious:YES];
  165.  
  166.     /* Modify it so that it will edit Rich Text and use the FontPanel. */
  167.  
  168.     [fe setCharFilter:NXEditorFilter];
  169.     [fe setFontPanelEnabled:YES];
  170.     [fe setMonoFont:NO];
  171.     [fe setOpaque:YES];
  172.  
  173.     /*
  174.      * Determine the minimum and maximum size that the Text object can be.
  175.      * We let the Text object grow out to the edges of the GraphicView,
  176.      * but no further.
  177.      */
  178.  
  179.     [view getBounds:&viewBounds];
  180.     maxSize.width = viewBounds.origin.x+viewBounds.size.width-bounds.origin.x;
  181.     maxSize.height = bounds.origin.y+bounds.size.height-viewBounds.origin.y;
  182.     if (!bounds.size.height && !bounds.size.width) {
  183.     bounds.origin.y -= floor([fe lineHeight] / 2.0);
  184.     bounds.size.height = [fe lineHeight];
  185.     bounds.size.width = 5.0;
  186.     }
  187.     frame = bounds;
  188.     [view convertRect:&frame fromView:[view superview]];    // ack!
  189.     [fe setMinSize:&bounds.size];
  190.     [fe setMaxSize:&maxSize];
  191.     [fe setFrame:&frame];
  192.     [fe setVertResizable:YES];
  193.  
  194.     /*
  195.      * If we already have text, then put it in the Text object (allowing
  196.      * the Text object to grow downward if necessary), otherwise, put
  197.      * no text in, set some initial parameters, and allow the Text object
  198.      * to grow horizontally as well as vertically
  199.      */
  200.  
  201.     if (data) {
  202.     [fe setHorizResizable:NO];
  203.     stream = NXOpenMemory(data, length, NX_READONLY);
  204.     [fe readRichText:stream];
  205.     NXCloseMemory(stream, NX_SAVEBUFFER);
  206.     } else {
  207.     [fe setHorizResizable:YES];
  208.     [fe setText:""];
  209.     [fe setAlignment:NX_LEFTALIGNED];
  210.     [fe setSelColor:[self textColor]];
  211.     [fe unscript:self];
  212.     }
  213.  
  214.     /*
  215.      * Add the Text object to the view heirarchy and set self as its delegate
  216.      * so that we will receive the textDidEnd:endChar: message when editing
  217.      * is finished.
  218.      */
  219.  
  220.     [fe setDelegate:self];
  221.     [view addSubview:fe];
  222.  
  223.     /*
  224.      * Make it the first responder.
  225.      */
  226.  
  227.     [[view window] makeFirstResponder:fe];
  228.  
  229.     /* Change the ruler to be a text ruler. */
  230.  
  231.     [fe tryToPerform:@selector(showTextRuler:) with:fe];
  232.  
  233.     /*
  234.      * Either pass the mouse-down event on to the Text object, or set
  235.      * the selection at the beginning of the text.
  236.      */
  237.  
  238.     if (event) {
  239.     [fe selectNull];    /* eliminates any existing selection */
  240.     [fe mouseDown:event];
  241.     } else {
  242.     [fe setSel:0:0];
  243.     }
  244.  
  245.     return YES;
  246. }
  247.  
  248. - draw
  249.  /*
  250.   * If the region has already been created, then we must draw the text.
  251.   * To do this, we first load up the shared drawText Text object with
  252.   * our rich text.  We then set the frame of the drawText object
  253.   * to be our bounds.  Finally, we add the Text object as a subview of
  254.   * the view that is currently being drawn in ([NXApp focusView])
  255.   * and tell the Text object to draw itself.  We then remove the Text
  256.   * object view from the view heirarchy.
  257.   */
  258. {
  259.     NXStream *stream;
  260.  
  261.     if (data) {
  262.     stream = NXOpenMemory(data, length, NX_READONLY);
  263.     [drawText readRichText:stream];
  264.     NXCloseMemory(stream, NX_SAVEBUFFER);
  265.     [drawText setFrame:&bounds];
  266.     [[NXApp focusView] addSubview:drawText];
  267.     [drawText display];
  268.     [drawText removeFromSuperview];
  269.     if (DrawStatus == Resizing) {
  270.         PSsetgray(NX_LTGRAY);
  271.         NXFrameRect(&bounds);
  272.     }
  273.     }
  274.  
  275.     return self;
  276. }
  277.  
  278. - performTextMethod:(SEL)aSelector with:(void *)anArgument
  279. {
  280.     int maxlen;
  281.     char *buffer;
  282.     NXStream *stream;
  283.  
  284.     if (data) {
  285.     stream = NXOpenMemory(data, length, NX_READONLY);
  286.     [drawText readRichText:stream];
  287.     NXCloseMemory(stream, NX_SAVEBUFFER);
  288.     [drawText setFrame:&bounds];
  289.     if (!drawWindow) drawWindow = [Window new];
  290.     [[drawWindow contentView] addSubview:drawText];
  291.     [drawText selectAll:self];
  292.     [drawText perform:aSelector with:anArgument];
  293.     [drawText removeFromSuperview];
  294.     [drawText setSel:0 :0];
  295.     font = [drawText font];
  296.     stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
  297.     [drawText writeRichText:stream];
  298.     NXGetMemoryBuffer(stream, &buffer, &length, &maxlen);
  299.     NX_ZONEMALLOC([self zone], data, char, length);
  300.     bcopy(buffer, data, length);
  301.     NXCloseMemory(stream, NX_FREEBUFFER);
  302.     }
  303.  
  304.     return self;
  305. }
  306.  
  307. - changeFont:sender
  308. {
  309.     [self performTextMethod:@selector(changeFont:) with:sender];
  310.     return self;
  311. }
  312.  
  313. - font
  314. {
  315.     NXStream *stream;
  316.  
  317.     if (!font && data) {
  318.     stream = NXOpenMemory(data, length, NX_READONLY);
  319.     [drawText readRichText:stream];
  320.     NXCloseMemory(stream, NX_SAVEBUFFER);
  321.     [drawText setSel:0 :0];
  322.     font = [drawText font];
  323.     }
  324.  
  325.     return font;
  326. }
  327.  
  328. - (BOOL)isOpaque
  329.  /*
  330.   * We are never opaque.
  331.   */
  332. {
  333.     return NO;
  334. }
  335.  
  336. - (BOOL)isValid
  337.  /*
  338.   * Any size TextGraphic is valid (since we fix up the size if it is
  339.   * too small in our override of create:in:).
  340.   */
  341. {
  342.     return YES;
  343. }
  344.  
  345. - (NXColor)textColor
  346. {
  347.     return NX_COLORBLACK;
  348. }
  349.  
  350. - (NXColor)lineColor
  351. {
  352.     return NX_COLORCLEAR;
  353. }
  354.  
  355. - (NXColor)fillColor
  356. {
  357.     return NX_COLORCLEAR;
  358. }
  359.  
  360. - (BOOL)wantsTextColor
  361. {
  362.     return YES;
  363. }
  364.  
  365. - (BOOL)wantsLineColor
  366. {
  367.     return NO;
  368. }
  369.  
  370. - (BOOL)wantsFillColor
  371. {
  372.     return NO;
  373. }
  374.  
  375. /* Text object delegate methods */
  376.  
  377. /*
  378.  * If we have more than one line, turn off horizontal resizing.
  379.  */
  380. - textDidResize:textObject oldBounds:(const NXRect *)oldBounds invalid:(NXRect *)invalidRect
  381. {
  382. NXSelPt start,end;
  383.  
  384.      [textObject getSel:&start :&end];
  385.      if(start.line || end.line)
  386.     [textObject setHorizResizable:NO];
  387.     return self;
  388. }
  389.  
  390. - textDidEnd:textObject endChar:(unsigned short)endChar
  391. /*
  392.  * This method is called when ever first responder is taken away from a
  393.  * currently editing TextGraphic (i.e. when the user is done editing and
  394.  * chooses to go do something else).  We must extract the rich text the user
  395.  * has typed from the Text object, and store it away.  We also need to
  396.  * get the frame of the Text object and make that our bounds (but,
  397.  * remember, since the Text object must be a subview of a flipped view,
  398.  * we need to convert the bounds rectangle to the coordinates of the
  399.  * unflipped GraphicView).  If the Text object is empty, then we remove
  400.  * this TextGraphic from the GraphicView and delayedFree: it.
  401.  * We must remove the Text object from the view heirarchy and, since
  402.  * this Text object is going to be reused, we must set its delegate
  403.  * back to nil.
  404.  *
  405.  * For further explanation of the two "ack!" lines, see edit:in: above.
  406.  */
  407. {
  408.     int maxlen;
  409.     char *buffer;
  410.     NXStream *stream;
  411.     NXRect oldBounds;
  412.     id editView, graphicView;
  413.  
  414.     if (data) {
  415.     NX_FREE(data);
  416.     data = NULL;
  417.     }
  418.  
  419.     editView = [textObject superview];
  420.     graphicView = [editView superview];                // ack!
  421.  
  422.     if ([textObject textLength]) {
  423.     stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
  424.     [textObject writeRichText:stream];
  425.     NXGetMemoryBuffer(stream, &buffer, &length, &maxlen);
  426.     NX_ZONEMALLOC([self zone], data, char, length);
  427.     bcopy(buffer, data, length);
  428.     NXCloseMemory(stream, NX_FREEBUFFER);
  429.     oldBounds = bounds;
  430.     [textObject getFrame:&bounds];
  431.     [editView convertRect:&bounds toView:graphicView];    // ack!
  432.     NXUnionRect(&bounds, &oldBounds);
  433.     [graphicView cache:&oldBounds];
  434.     [[graphicView window] flushWindow];
  435.     [graphicView dirty];
  436.     } else {
  437.     [graphicView removeGraphic:self];
  438.     }
  439.  
  440.     [[self class] perform:@selector(hideRuler:) with:graphicView afterDelay:500 cancelPrevious:YES];
  441.  
  442.     [textObject removeFromSuperview];
  443.     [textObject setDelegate:nil];
  444.     [textObject setSel:0 :0];
  445.     font = [textObject font];
  446.  
  447.     return self;
  448. }
  449.  
  450. /* Archiving methods */
  451.  
  452. - awake
  453. {
  454.     initClassVars();
  455.     return [super awake];
  456. }
  457.  
  458. - write:(NXTypedStream *)stream
  459.  /*
  460.   * Writes the TextGraphic out to the typed stream.
  461.   */
  462. {
  463.     [super write:stream];
  464.     NXWriteTypes(stream, "i", &length);
  465.     NXWriteArray(stream, "c", length, data);
  466.     return self;
  467. }
  468.  
  469. - read:(NXTypedStream *)stream
  470.  /*
  471.   * Reads the TextGraphic in from the typed stream.
  472.   * This is versioned.  The old way we used to implement
  473.   * this class included using a Cell object.  Now we
  474.   * use the Text object directly.
  475.   */
  476. {
  477.     id cell;
  478.     int maxlen;
  479.     NXStream *s;
  480.     char *buffer;
  481.  
  482.     [super read:stream];
  483.     if (NXTypedStreamClassVersion(stream, [self name]) < 1) {
  484.     NXReadTypes(stream, "@", &cell);
  485.     [drawText setText:[cell stringValue]];
  486.     font = [cell font];
  487.     [drawText setFont:[cell font]];
  488.     [drawText setTextColor:[self lineColor]];
  489.     s = NXOpenMemory(NULL, 0, NX_WRITEONLY);
  490.     [drawText writeRichText:s];
  491.     NXGetMemoryBuffer(s, &buffer, &length, &maxlen);
  492.     NX_ZONEMALLOC([self zone], data, char, length);
  493.     bcopy(buffer, data, length);
  494.     NXCloseMemory(s, NX_FREEBUFFER);
  495.     } else {
  496.     NXReadTypes(stream, "i", &length);
  497.     NX_ZONEMALLOC([self zone], data, char, length);
  498.     NXReadArray(stream, "c", length, data);
  499.     }
  500.  
  501.     return self;
  502. }
  503.  
  504. @end
  505.